package com.commons.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.commons.annotation.DataPermission;
import com.commons.annotation.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.*;


public class QueryHelp {

    private static Logger LOGGER=LoggerFactory.getLogger(QueryHelp.class);

    public static <R,Q>Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb){
        List<Predicate> list=new ArrayList<>();
        if(query==null){
            return cb.and(list.toArray(new Predicate[0]));
        }
        //数据权限验证
        DataPermission permission=query.getClass().getAnnotation(DataPermission.class);

        if(permission!=null){
            //获取数据权限
            List<Long> dataScopes=SecurityUtils.getCurrentUserDataScope();
            if(CollectionUtil.isNotEmpty(dataScopes)){
                if(StringUtils.isNotBlank(permission.joinName())&&StringUtils.isNotBlank(permission.fieldName())){
                    Join join=root.join(permission.joinName(),JoinType.LEFT);
                    list.add(getExpression(permission.fieldName(),join,root).in(dataScopes));
                }else if(StringUtils.isBlank(permission.joinName())&&StringUtils.isNotBlank(permission.fieldName())){
                    list.add(getExpression(permission.fieldName(),null,root).in(dataScopes));
                }
            }
        }
        try {
            Map<String,Join> joinKey=new HashMap<>();
            List<Field> fields =getAllFields(query.getClass(),new ArrayList<>());
            for(Field field:fields){
                //是否可访问
                boolean accessible  = field.isAccessible();
                // 设置对象的访问权限，保证对private的属性的访问
                field.setAccessible(true);
                Query q=field.getAnnotation(Query.class);
                if(q!=null){
                    String propName = q.PropName();
                    String joinName = q.joinName();
                    String blurry = q.blurry();
                    String attributeName=isBlank(propName)?field.getName():propName;
                    Class<?> fieldType = field.getType();
                    Object val = field.get(query);
                    if(ObjectUtil.isNull(val)||"".equals(val)){
                        continue;
                    }
                    Join join=null;
                    //模糊多字段
                    if(ObjectUtil.isNotEmpty(blurry)){
                        String[] blurrys = blurry.split(",");
                        List<Predicate> orPredicate=new ArrayList<>();
                        for(String s:blurrys){
                            orPredicate.add(cb.like(root.get(s).as(String.class),"%"+val.toString()+"%"));
                        }
                        Predicate[] p = new Predicate[orPredicate.size()];
                        list.add(cb.or(orPredicate.toArray(p)));
                        continue;
                    }
                    if(ObjectUtil.isNotEmpty(joinName)){
                        if(join==null){
                            String[] joinNames  = joinName.split(">");
                            for(String name:joinNames){
                                switch (q.join()) {
                                    case LEFT:
                                        if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)) {
                                            join = join.join(name, JoinType.LEFT);
                                        } else {
                                            join = join.join(name, JoinType.LEFT);
                                        }
                                        break;
                                    case RIGHT:
                                        if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)) {
                                            join = join.join(name, JoinType.RIGHT);
                                        } else {
                                            join = join.join(name, JoinType.RIGHT);
                                        }
                                        break;
                                    case INNER:
                                        if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)) {
                                            join = join.join(name, JoinType.INNER);
                                        } else {
                                            join = join.join(name, JoinType.INNER);
                                        }
                                        break;
                                    default:
                                        break;
                                }
                            }
                            joinKey.put(joinName,join);
                        }
                        switch (q.type()){
                            case EQUAL:
                                list.add(cb.equal(getExpression(attributeName,join,root).
                                        as(fieldType),val));
                                break;
                            case GEATER_THAN:
                                list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root).
                                        as((Class<? extends Comparable> ) fieldType),(Comparable)val));
                                break;
                            case LESS_THAN:
                                list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root).
                                        as((Class<? extends Comparable> ) fieldType),(Comparable)val));
                                break;
                            case LESS_THAN_NQ:
                                list.add(cb.lessThan(getExpression(attributeName,join,root).
                                        as((Class<? extends Comparable> ) fieldType),(Comparable)val));
                            case INNER_LIKE:
                                list.add(cb.like(getExpression(attributeName,join,root).
                                        as(String.class),"%"+val.toString()+"%"));
                                break;
                            case LEFT_LIKE:
                                list.add(cb.like(getExpression(attributeName,join,root)
                                        .as(String.class), "%" + val.toString()));
                                break;
                            case RIGHT_LIKE:
                                list.add(cb.like(getExpression(attributeName,join,root)
                                        .as(String.class), val.toString() + "%"));
                                break;
                            case IN:
                                if (CollUtil.isNotEmpty((Collection<Object>)val)) {
                                    list.add(getExpression(attributeName,join,root).in((Collection<Object>) val));
                                }
                                break;
                            case NOT_IN:
                                if (CollUtil.isNotEmpty((Collection<Object>)val)) {
                                    list.add(getExpression(attributeName,join,root).in((Collection<Object>) val).not());
                                }
                                break;
                            case NOT_EQUAL:
                                list.add(cb.notEqual(getExpression(attributeName,join,root), val));
                                break;
                            case NOT_NULL:
                                list.add(cb.isNotNull(getExpression(attributeName,join,root)));
                                break;
                            case IS_NULL:
                                list.add(cb.isNull(getExpression(attributeName,join,root)));
                                break;
                            case BETWEEN:
                                List<Object> between = new ArrayList<>((List<Object>)val);
                                if(between.size() == 2){
                                    list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),
                                            (Comparable) between.get(0), (Comparable) between.get(1)));
                                }
                                break;
                            case FIND_IN_SET:
                                list.add(cb.greaterThan(cb.function("FIND_IN_SET", Integer.class,
                                        cb.literal(val.toString()), root.get(attributeName)), 0));
                                break;
                            default: break;
                        }
                    }
                    field.setAccessible(accessible);
                }
            }
        }catch (Exception e){
            LOGGER.error(e.getMessage(),e);
        }
        int size = list.size();
        return cb.and(list.toArray(new Predicate[size]));
    }


    /**
     *
     * @param attributeName
     * @param join
     * @param root 查询的根对象
     * @param <T>
     * @param <R>
     * @return
     */
    private static <T,R> Expression<T> getExpression(String attributeName,Join join,Root<R> root){
        if(ObjectUtil.isNotEmpty(join)){
            return join.get(attributeName);
        }else{
            return root.get(attributeName);
        }
    }


    /**
     * java.lang.reflect.Field  获取类中字段信息
     * class,getSuperclass() 获取父类
     * @param clazz
     * @param fields
     * @return
     */
    public static List<Field> getAllFields(Class clazz,List<Field> fields){
        if(clazz!=null){
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            getAllFields(clazz.getSuperclass(),fields);
        }
        return fields;
    }

    //用于判断指定字符是否为空白字符，空白符包含：空格、tab键、换行符
    private static boolean isBlank(CharSequence cs){
        int strLen;
        if(cs==null||(strLen=cs.length())==0){
            return true;
        }
        for(int i=0;i<strLen;i++){
            if(!Character.isWhitespace(cs.charAt(i))){
                return false;
            }
        }
        return true;
    }
}
